package timbap.visualizer.impl.plugin.configuration;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import timbap.visualizer.api.context.PluginContext;
import timbap.visualizer.api.exceptions.InvalidPluginEntryException;
import timbap.visualizer.api.exceptions.PluginTypeMismatchException;
import timbap.visualizer.api.plugin.Plugin;
import timbap.visualizer.api.plugin.configuration.AbstractPluginManager;
import timbap.visualizer.api.plugin.configuration.binding.PluginBinding;
import timbap.visualizer.api.plugin.configuration.binding.PluginType;
import timbap.visualizer.api.plugin.configuration.binding.TransitionBinding;
import timbap.visualizer.api.plugin.container.ModeController;
import timbap.visualizer.api.plugin.container.PluginContainer;

import java.io.File;
import java.io.FileFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DefaultPluginManagerImpl extends AbstractPluginManager {

    private static final Log LOG = LogFactory.getLog(DefaultPluginManagerImpl.class);

    protected URLClassLoader pluginClassLoader;
    protected Map<String,Plugin> pluginRegistry;

    public void readPluginConfiguration(PluginContext pPluginContext) {

        LOG.info("Preparing plugin classloader ...");
        File[] tPluginJars = new File("plugins").listFiles(
                new FileFilter() {
                    public boolean accept(File pathname) {
                        return pathname.getName().endsWith(".jar");
                    }
                }
        );
        if (tPluginJars == null) {
            tPluginJars = new File[0];
        }
        LOG.info("Found " + tPluginJars.length + " plugin jar file(s) in plugins directory:");

        List<URL> tPluginJarURLs = new ArrayList<URL>(tPluginJars.length);
        for (File tPluginJar: tPluginJars) {
            LOG.info("o " + tPluginJar.getName());
            try {
                tPluginJarURLs.add(tPluginJar.toURI().toURL());
            } catch (MalformedURLException e) {
                LOG.error(e,e);
            }
        }
        pluginClassLoader = new URLClassLoader(tPluginJarURLs.toArray(new URL[tPluginJarURLs.size()]));
        pluginRegistry = new HashMap<String,Plugin>();

        super.readPluginConfiguration(pPluginContext);
    }

    private Plugin loadPluginInstance(String tClassName) {
        Plugin tPlugin = null;
        if (tClassName != null && tClassName.length() > 0) {
            if (!pluginRegistry.containsKey(tClassName)) {
                try {
                    if (!tClassName.contains(".")) {
                        tClassName = pluginConfig.getDefaultPluginPackage() + "." + tClassName;
                    }
                    if (!pluginRegistry.containsKey(tClassName)) {
                        Class tPluginClass = pluginClassLoader.loadClass(tClassName);
                        if (Plugin.class.isAssignableFrom(tPluginClass)) {
                            tPlugin = (Plugin) tPluginClass.newInstance();
                            pluginRegistry.put(tClassName, tPlugin);
                        } else {
                            throw new InvalidPluginEntryException(tPluginClass);
                        }
                    } else {
                        tPlugin = pluginRegistry.get(tClassName);
                    }
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            } else {
                tPlugin = pluginRegistry.get(tClassName);
            }
        }
        return tPlugin;
    }

    protected Plugin loadPluginInstance(PluginBinding pPluginBinding) {
        String tClassName = pPluginBinding.getClassName();
        Plugin tPlugin = loadPluginInstance(tClassName);

        if (tPlugin != null) {
            if (Arrays.asList(PluginType.FORK, PluginType.SWITCH).contains(pPluginBinding.getType())) {
                if (tPlugin instanceof PluginContainer) {
                    for (PluginBinding tChildPluginBinding: pPluginBinding.getSubPlugins()) {
                        Plugin tChildPlugin = loadPluginInstance(tChildPluginBinding);
                        if (tChildPlugin != null) {
                            ((PluginContainer)tPlugin).addPlugin(tChildPlugin);
                        }
                    }
                } else {
                    throw new PluginTypeMismatchException(tPlugin, PluginContainer.class);
                }
                if (tPlugin instanceof ModeController) {
                    ModeController tModeController = (ModeController) tPlugin;
                    for (TransitionBinding tTransitionBinding : pPluginBinding.getTransitions()) {
                        Plugin tTargetPlugin = loadPluginInstance(tTransitionBinding.getTo());
                        if (tTargetPlugin instanceof ModeController) {
                            ModeController tTargetModeController = (ModeController) tTargetPlugin;
                            switch (tTransitionBinding.getType()) {
                                case BACK:
                                    tModeController.setPreviousModeController(tTargetModeController); break;
                                case NEXT:
                                    tModeController.setNextModeController(tTargetModeController); break;
                            }
                        }
                    }
                }
            }
        }
        return tPlugin;
    }

    public Plugin getPlugin(Class pClass) {
        return pluginRegistry.get(pClass.getSimpleName());
    }

    public Plugin getPlugin(String pSimpleClassName) {
        return pluginRegistry.get(pSimpleClassName);
    }

}
